//
// Copyright 2009 and later Google Inc.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the
//   Free Software Foundation Inc.,
//   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
// Author: Casey Burkhardt (caseyburkhardt)
// Description: File serves as the main operational source for the analyzer.
//              It parses command line arguments, reads input data, generates
//              result objects, performs per instruction calculations, and
//              writes the output files to the file system.

#include <math.h>
#include <sstream>
#include <string>
#include <vector>

#include "Result.h"

// Constants
const char kResultIndexFileName[] = "successfulexecution.txt";
const char kResultBaselineFileName[] = "test_baseline_results.txt";
const char kTargetDirectoryFlag[] = "--results=";
const char kInstructionCountFlag[] = "--instructions=";
const char kIterationCountFlag[] = "--iterations=";
const char kHelpFlag[] = "--help";
const char kVerbosityFlag[] = "--verbose";
const char kHelpText[] =
"Usage: Analyzer [ARGUMENT=VALUE]... [ARGUMENT]...\n"
"Exhaustively generates assembly file tests based on operations and operands\n"
"data files.\n\n"
"The following command line arguments are required.\n"
"  --results=          The directory containing the output generated by the\n"
"                        Runner script.\n\n"
"The following command line arguments are optional.\n"
"  --instructions=     The number of instructions contained within the body\n"
"                        loop of each assembly test.\n"
"  --iterations=       The number of times each assembly test iterates over \n"
"                        the body instructions.\n"
"Should the --instructions= or --iterations= command line arguments not be\n"
"provided, this information will be read from test_set.dat within the results\n"
"directory.\n"
"  --verbose           Display status messages for each processed result. \n"
"  --help              Display this help message";
const char kFileCommentCharacter = '#';
const char kResultFileNamePrefix[] = "test_";
const char kResultFileNameSuffix[] = "_results.txt";
const char kTestSetDataFile[] = "test_set.dat";
const char kBaselineResultFileNameBody[] = "baseline";
const char kBaselineSourceFileName[] = "baseline.s";
const char kTestSetResultFileName[] = "results.txt";
const char kFileDelimiter[] = ", ";
const int kMaxBufferSize = 512;

// Checks to see if a given command line flag is present within a given command
// line argument.
bool ArgumentHasFlag(std::string argument, std::string flag) {
  return strncmp(argument.c_str(), flag.c_str(), flag.length()) == 0;
}

// This function returns the string value of a command line argument, given the
// actual argument and anticipated argument flag
std::string ParseCommandLineString(std::string arg, const std::string flag) {
  return arg.substr(flag.length());
}

// This function returns the integer value of a command line argument, given the
// actual argument and anticipated argument flag
int ParseCommandLineInt(std::string arg, const std::string flag) {
  arg = arg.substr(flag.length());
  return atoi(arg.c_str());
}

// The calculation function that translates the number of raw CPU events into
// the PIEC (Per Instruction Event Count) given the number of raw events,
// baseline events, number of iterations, and instructions.
int CalculateEventsPerInstruction(long int raw_events, long int baseline_events,
                                  int number_instructions,
                                  int number_iterations) {
  return round((double)(raw_events - baseline_events) / (number_instructions *
      number_iterations));
}

// This function returns the number of uncommented lines within a file.  This is
// used to determine the size of the array used to hold the Result objects.
int CountUncommentedLines(std::string file_name, char comment_char) {
  int result = 0;
  FILE *file_in;
  char line[kMaxBufferSize];

  if ((file_in = fopen(file_name.c_str(), "r")) == 0) {
    perror("Unable to open data file");
    exit(1);
  }
  while (fgets(line, sizeof(line), file_in) != NULL) {
    if (line[0] != comment_char) {
      ++result;
    }
  }
  if (fclose(file_in) != 0) {
    perror("Unable to close file");
    exit(1);
  }
  return result;
}

// This function returns a fully populated Result object given the name of a
// result file, result directory, and other information needed to calculate
// actual event per instruction counts.
Result* GenerateResult(const std::string index_line,
                       const std::string result_directory,
                       long int baseline_event_count, int number_instructions,
                       int number_iterations) {
  // Variables related to file operation
  // Remove the '.s' extension from the filename and replace with result suffix.
  std::string file_name = index_line.substr(0, index_line.length() - 3);
  file_name = result_directory + "/" + kResultFileNamePrefix + file_name
              + kResultFileNameSuffix;
  FILE *file_in;
  std::string operation_name, addressing_mode;
  char line[kMaxBufferSize];
  char holder[kMaxBufferSize];

  // Variables related to actual events per instruction calculation
  int events_per_instruction = -1;
  long int raw_events_per_result = 0;

  if ((file_in = fopen(file_name.c_str(), "r")) == 0) {
    perror("Unable to open test results file");
    printf("%s\n", file_name.c_str());
    exit(1);
  }
  if (fgets(line, sizeof(line), file_in) == NULL) {
    printf("Test results file is empty.\n");
    printf("%s\n", file_name.c_str());
    exit(1);
  } else {
    raw_events_per_result = atoi(strtok(line, " "));
  }
  if (fclose(file_in) != 0) {
    perror("Unable to close test results file");
    printf("%s\n", file_name.c_str());
    exit(1);
  }
  events_per_instruction = CalculateEventsPerInstruction(raw_events_per_result,
                                                         baseline_event_count,
                                                         number_instructions,
                                                         number_iterations);
  strncpy(holder, index_line.c_str(), index_line.length());
  if (index_line.find('_') == -1) {
    // Test has no operands, addressing mode will be blank.
    operation_name = strtok(NULL, ".");
    addressing_mode = "";
  } else {
    operation_name = (strtok(holder, "_"));
    addressing_mode = strtok(NULL, ".");
  }

  // Initialize and return pointer to fully populated Result object.
  Result* result = new Result(operation_name, addressing_mode,
                              events_per_instruction, raw_events_per_result);
  return result;
}

int main(int argc, char* argv[]) {
  // Variables used for array size declaration and target referencing
  int number_iterations = 0;
  int number_instructions = 0;
  std::string target_directory;

  // Variables used for file processing and array indexing
  std::string file_name;
  FILE *file_in, *file_out;
  char line[kMaxBufferSize];

  // Variables used for event tracking and baseline correction
  std::string event_name;
  long int baseline_raw_event_count = 0;

  // Used to track verbose mode status
  bool verbose = false;

  // Command Line Argument Parsing
  for (int i = 1; i < argc; ++i) {
    if (ArgumentHasFlag(argv[i], kTargetDirectoryFlag)) {
      target_directory = ParseCommandLineString(argv[i], kTargetDirectoryFlag);
    } else if (ArgumentHasFlag(argv[i], kInstructionCountFlag)) {
      number_instructions = ParseCommandLineInt(argv[i], kInstructionCountFlag);
    } else if (ArgumentHasFlag(argv[i], kIterationCountFlag)) {
      number_iterations = ParseCommandLineInt(argv[i], kIterationCountFlag);
    } else if (ArgumentHasFlag(argv[i], kHelpFlag)) {
      printf("%s\n", kHelpText);
    } else if (ArgumentHasFlag(argv[i], kVerbosityFlag)) {
      verbose = true;
    } else {
      fprintf(stderr, "Ignoring Unknown Command Line Argument: %s\n", argv[i]);
    }
  }

  // Check (and replace if necessary) Command Line Argument Values
  if (target_directory == "") {
    fprintf(stderr, "Results Target Directory Flag Missing or Invalid\n");
    exit(1);
  }

  // Determine the instruction and iteration count for the test set if it wasn't
  // already passed in as a command line argument.
  if (number_instructions <= 0 && number_iterations <= 0) {
    file_name = target_directory + "/" + kTestSetDataFile;
    if ((file_in = fopen(file_name.c_str(), "r")) == 0) {
      perror("Unable to open test set data file");
      exit(1);
    }
    while (fgets(line, sizeof(line), file_in) != NULL) {
      if (line[0] != kFileCommentCharacter) {
        if (ArgumentHasFlag(line, kInstructionCountFlag)) {
          number_instructions = ParseCommandLineInt(line,
                                                    kInstructionCountFlag);
        } else if (ArgumentHasFlag(line, kIterationCountFlag)) {
          number_iterations = ParseCommandLineInt(line, kIterationCountFlag);
        }
      }
    }
    if (fclose(file_in) != 0) {
      perror("Unable to close test set data file");
      exit(1);
    }
    if (number_instructions <= 0 || number_iterations <= 0) {
      perror("Test set data file contained invalid data\n");
      exit(1);
    }
    if (verbose) {
      printf("Test Set Instructions: %d\nTest Set Iterations: %d\n",
             number_instructions, number_iterations);
    }
  }

  // Determine the raw baseline CPU event count and event name
  file_name = target_directory + "/" + kResultFileNamePrefix
              + kBaselineResultFileNameBody + kResultFileNameSuffix;
  if ((file_in = fopen(file_name.c_str(), "r")) == 0) {
    perror("Unable to open baseline test results file");
    exit(1);
  }
  if (fgets(line, sizeof(line), file_in) == NULL) {
    fprintf(stderr, "Baseline test results file is empty.\n");
    exit(1);
  } else {
    baseline_raw_event_count = atoi(strtok(line, " "));
    event_name = strtok(NULL, " ");
  }
  if (fclose(file_in) != 0) {
    perror("Unable to close test set data file");
    exit(1);
  }

  if (baseline_raw_event_count <= 0 || event_name == "") {
    fprintf(stderr, "Baseline test results is invalid.\n");
    exit(1);
  } else if (verbose) {
    printf("Test Set Baseline Raw Event Count: %ld\nTest Set Event Name: %s\n",
           baseline_raw_event_count, event_name.c_str());
  }


  // Holds the instances of Results Objects
  std::vector<Result*> results;

  // Iterate over all files in the successfully compiled index and calculate
  // actual event counts per instruction and populate Result object data
  // members.
  file_name = target_directory + "/" + kResultIndexFileName;
  if ((file_in = fopen(file_name.c_str(), "r")) == 0) {
    perror("Unable to open test results index file");
    exit(1);
  }
  while (fgets(line, sizeof(line), file_in) != NULL) {
    if (strncmp(line, kBaselineSourceFileName,
               strlen(kBaselineSourceFileName)) != 0) {
      // If the result isn't the baseline result.
      results.push_back(GenerateResult(line, target_directory,
                                       baseline_raw_event_count,
                                       number_iterations, number_instructions));
      if (verbose) {
        if (results.back()->addressing_mode() == "") {
          printf("Generated Result: %s\n",
                 results.back()->operation_name().c_str());
        } else {
          printf("Generated Result: %s_%s\n",
                 results.back()->operation_name().c_str(),
                 results.back()->addressing_mode().c_str());
        }
      }
    }
  }
  if (fclose(file_in) != 0) {
    perror("Unable to close test results index file");
    exit(1);
  }

  // Output results to a result file
  file_name = target_directory + "/" + kTestSetResultFileName;
  if ((file_out = fopen(file_name.c_str(), "w")) == 0) {
    perror("Unable to open results output file.\n");
    exit(1);
  }
  fprintf(file_out, "%c %s", kFileCommentCharacter, event_name.c_str());
  for (int i = 0; i < results.size(); ++i) {
    if (results[i]->addressing_mode() != "") {
    fprintf(file_out, "%d, %s, %s\n", results[i]->events_per_instruction(),
            results[i]->operation_name().c_str(),
            results[i]->addressing_mode().c_str());
    } else {
      fprintf(file_out, "%d, %s\n", results[i]->events_per_instruction(),
              results[i]->operation_name().c_str());
    }
  }
  if (fclose(file_out) != 0) {
    perror("Unable to close results output file.\n");
    exit(1);
  }
  return(0);
}
